package com.cardone.generator.template;

import com.cardone.common.template.util.TemplateUtils;
import com.cardone.generator.mapper.*;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import freemarker.ext.beans.BeansWrapper;
import freemarker.template.Configuration;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.io.File;
import java.util.List;
import java.util.Map;

/**
 * spring + spring mvc + spring jdbc
 *
 * @author yaohaitao
 */
@Getter
@Setter
@Accessors(chain = true)
@Slf4j
public class RunSSSTemplate implements RunTemplate {
    private Configuration configuration;

    private Map<String, String> contextMap = Maps.newHashMap();

    private String extendName = "Oracle";

    private Map<String, Object> model = Maps.newHashMap();

    private String outputDir;

    private boolean overrideFile = true;

    private ProjectMapper projectMapper;

    private List<String> serialVersionUIDList = Lists.newArrayList();

    /**
     * 初始化
     */
    public void init() {
        Assert.notNull(this.projectMapper);

        Assert.notNull(this.outputDir);
    }

    private void initContext() {
        final String packageDir = StringUtils.replace(this.projectMapper.getPackageCode(), ".", File.separator);

        this.contextMap.put("outputDir", this.outputDir);

        this.contextMap.put("packageDir", packageDir);

        this.model.put("statics", BeansWrapper.getDefaultInstance().getStaticModels());

        this.model.put("projectMapper", this.projectMapper);

        this.model.put("packageCode", this.projectMapper.getPackageCode());

        this.model.put("extendName", this.extendName);

        this.model.put("projectCode", this.projectMapper.getCode());

        this.contextMap.put("extendName", this.extendName);

        this.contextMap.put("projectCode", this.projectMapper.getCode());
    }

    private void makeFile(final String templateString, final String templateName, final Boolean overrideFile) throws Exception {
        RunSSSTemplate.log.info(templateString);

        RunSSSTemplate.log.info(templateName);

        final String filePathName = TemplateUtils.processString(templateString, this.contextMap);

        RunSSSTemplate.log.info(filePathName);

        final File file = new File(filePathName);

        if (file.exists()) {
            RunSSSTemplate.log.info("file.exists():true");

            if (this.overrideFile && overrideFile) {
                FileUtils.deleteQuietly(file);
            } else {
                return;
            }
        }

        final freemarker.template.Template template = this.configuration.getTemplate(templateName);

        this.modelPutSerialVersionUID();

        final String data = FreeMarkerTemplateUtils.processTemplateIntoString(template, this.model);

        FileUtils.writeStringToFile(file, data);
    }

    private void markController() throws Exception {
        final String javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperCode!}/controller/${filename!}Controller.java";

        this.makeFile(javaTemplateString, "Controller.ftl", true);
    }

    private void markDao() throws Exception {
        String javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperCode!}/dao/${filename!}Dao.java";

        this.makeFile(javaTemplateString, "Dao.java.ftl", true);

        javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperCode!}/dao/${filename!}JdbcDao.java";

        this.makeFile(javaTemplateString, "JdbcDao.java.ftl", true);
    }

    private void markDto() throws Exception {
        final String javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperCode!}/dto/${filename!}Dto.java";

        this.makeFile(javaTemplateString, "Dto.java.ftl", true);
    }

    private void markPo() throws Exception {
        final String javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperCode!}/po/${filename!}.java";

        this.makeFile(javaTemplateString, "Po.java.ftl", true);
    }

    private void markService() throws Exception {
        String javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperCode!}/service/${filename!}Service.java";

        this.makeFile(javaTemplateString, "Service.java.ftl", true);

        javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperCode!}/service/${filename!}DefaultService.java";

        this.makeFile(javaTemplateString, "DefaultService.java.ftl", true);
    }

    private void markSpringConfig() throws Exception {
        String javaTemplateString = "${outputDir!}/src/main/resources/${projectCode!}/${moduleMapperCode!}/applicationContext-${filename!}.xml";

        this.makeFile(javaTemplateString, "applicationContext-service.ftl", true);

        javaTemplateString = "${outputDir!}/src/main/resources/${projectCode!}/${moduleMapperCode!}/applicationContext-${filename!}-update.xml";

        this.makeFile(javaTemplateString, "applicationContext-update.ftl", true);
    }

    private void markSQL() throws Exception {
        final String root = "${outputDir!}/src/main/resources/${projectCode!}/sql/generator/${extendName?uncap_first}/${projectCode!}/${moduleMapperCode!}/${businessCode?uncap_first}/";

        String javaTemplateString = root + "updateByCode.ftl";

        this.makeFile(javaTemplateString, "updateByCode.ftl", true);

        javaTemplateString = root + "updateByIds.ftl";

        this.makeFile(javaTemplateString, "updateByIds.ftl", true);

        javaTemplateString = root + "findByCode.ftl";

        this.makeFile(javaTemplateString, "findByCode.ftl", true);

        javaTemplateString = root + "readByCode.ftl";

        this.makeFile(javaTemplateString, "readByCode.ftl", true);

        javaTemplateString = root + "insertByCode.ftl";

        this.makeFile(javaTemplateString, "insertByCode.ftl", true);

        javaTemplateString = root + "insertByNotExistsCode.ftl";

        this.makeFile(javaTemplateString, "insertByNotExistsCode.ftl", true);

        javaTemplateString = root + "deleteByCode.ftl";

        this.makeFile(javaTemplateString, "deleteByCode.ftl", true);

        javaTemplateString = root + "deleteByIds.ftl";

        this.makeFile(javaTemplateString, "deleteByIds.ftl", true);

        javaTemplateString = root + "whereByCode.ftl";

        this.makeFile(javaTemplateString, "whereByCode.ftl", true);
    }

    private void modelPutSerialVersionUID() {
        String serialVersionUID = RandomStringUtils.randomNumeric(18);

        while (StringUtils.startsWithAny(serialVersionUID, new String[]{"0", "9"}) || this.serialVersionUIDList.contains(serialVersionUID)) {
            serialVersionUID = RandomStringUtils.randomNumeric(18);
        }

        this.serialVersionUIDList.add(serialVersionUID);

        this.model.put("serialVersionUID", serialVersionUID);
    }

    @Override
    public void run() throws Exception {
        if (CollectionUtils.isEmpty(this.projectMapper.getModuleMapperList())) {
            return;
        }

        this.initContext();

        for (final ModuleMapper moduleMapper : this.projectMapper.getModuleMapperList()) {
            this.contextMap.put("moduleMapperCode", moduleMapper.getCode());

            this.model.put("moduleMapperCode", moduleMapper.getCode());

            this.model.put("moduleMapper", moduleMapper);

            if (CollectionUtils.isEmpty(moduleMapper.getBusinessMapperList())) {
                continue;
            }

            for (final BusinessMapper businessMapper : moduleMapper.getBusinessMapperList()) {
                businessMapper.setName(StringUtils.defaultString(businessMapper.getName(), moduleMapper.getName()));

                this.setBusiness(businessMapper);

                if (businessMapper.getEntityMapper() != null) {
                    this.markPo();

                    this.markDto();

                    this.markSQL();
                }

                this.markController();

                this.markDao();

                this.markService();

                this.markSpringConfig();
            }

            this.model.put("projectMapper", this.projectMapper);
        }
    }

    @Override
    public List<PoMapper> findListPoMapper() {
        final List<PoMapper> poMapperList = Lists.newArrayList();

        if (CollectionUtils.isEmpty(this.projectMapper.getModuleMapperList())) {
            return poMapperList;
        }

        for (final ModuleMapper moduleMapper : this.projectMapper.getModuleMapperList()) {
            if (CollectionUtils.isEmpty(moduleMapper.getBusinessMapperList())) {
                continue;
            }

            for (final BusinessMapper businessMapper : moduleMapper.getBusinessMapperList()) {
                if (StringUtils.isEmpty(businessMapper.getTableName())) {
                    continue;
                }

                final EntityMapper entityMapper = new EntityMapper();

                entityMapper.setCode(businessMapper.getCode());
                entityMapper.setTableName(businessMapper.getTableName());

                businessMapper.setEntityMapper(entityMapper);

                poMapperList.add(entityMapper);
            }
        }

        return poMapperList;
    }

    private void setBusiness(final BusinessMapper businessMapper) {
        this.contextMap.put("filename", businessMapper.getCode());

        this.contextMap.put("businessCode", businessMapper.getCode());

        this.model.put("businessCode", businessMapper.getCode());

        this.model.put("entityMapper", businessMapper.getEntityMapper());

        if (businessMapper.getEntityMapper() == null) {
            this.model.put("businessName", StringUtils.defaultIfBlank(businessMapper.getName(), businessMapper.getCode()));
        } else {
            this.model.put("businessName", StringUtils.defaultIfBlank(businessMapper.getEntityMapper().getRemarks(), businessMapper.getEntityMapper().getCode()));
        }

        this.model.put("businessMapper", businessMapper);
    }
}
